frontend/pages/e/[uuid]/details.tsx (view raw)
1import moment from 'moment';
2import Tooltip from '@mui/material/Tooltip';
3import IconButton from '@mui/material/IconButton';
4import Button from '@mui/material/Button';
5import Box from '@mui/material/Box';
6import Link from '@mui/material/Link';
7import Card from '@mui/material/Card';
8import Container from '@mui/material/Container';
9import TextField from '@mui/material/TextField';
10import Typography from '@mui/material/Typography';
11import PlaceOutlinedIcon from '@mui/icons-material/PlaceOutlined';
12import EventIcon from '@mui/icons-material/Event';
13import TuneIcon from '@mui/icons-material/Tune';
14import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
15import {useTheme} from '@mui/material/styles';
16import {DatePicker} from '@mui/x-date-pickers/DatePicker';
17import {PropsWithChildren, useState} from 'react';
18import {useTranslation} from 'react-i18next';
19import pageUtils from '../../../lib/pageUtils';
20import ShareEvent from '../../../containers/ShareEvent';
21import useEventStore from '../../../stores/useEventStore';
22import useToastStore from '../../../stores/useToastStore';
23import EventLayout, {TabComponent} from '../../../layouts/Event';
24import {
25 EventByUuidDocument,
26 useUpdateEventMutation,
27} from '../../../generated/graphql';
28import PlaceInput from '../../../containers/PlaceInput';
29
30interface Props {
31 eventUUID: string;
32 announcement?: string;
33}
34
35const Page = (props: PropsWithChildren<Props>) => {
36 return <EventLayout {...props} Tab={DetailsTab} />;
37};
38
39const DetailsTab: TabComponent = ({}) => {
40 const {t} = useTranslation();
41 const theme = useTheme();
42 const [updateEvent] = useUpdateEventMutation();
43 const addToast = useToastStore(s => s.addToast);
44 const setEventUpdate = useEventStore(s => s.setEventUpdate);
45 const event = useEventStore(s => s.event);
46 const [isEditing, setIsEditing] = useState(false);
47
48 if (!event) return null;
49
50 const onSave = async e => {
51 try {
52 const {uuid, ...data} = event;
53 const {id, travels, waitingPassengers, __typename, ...input} = data;
54 await updateEvent({
55 variables: {
56 uuid,
57 eventUpdate: {
58 ...input,
59 },
60 },
61 refetchQueries: ['eventByUUID'],
62 });
63 setIsEditing(false);
64 } catch (error) {
65 console.error(error);
66 addToast(t('event.errors.cant_update'));
67 }
68 };
69
70 const modifyButton = isEditing ? (
71 <Tooltip
72 title={t('event.details.save')}
73 sx={{
74 position: 'absolute',
75 top: theme.spacing(2),
76 right: theme.spacing(2),
77 }}
78 >
79 <IconButton color="primary" onClick={onSave}>
80 <CheckCircleOutlineIcon />
81 </IconButton>
82 </Tooltip>
83 ) : (
84 <Tooltip
85 title={t('event.details.modify')}
86 sx={{
87 position: 'absolute',
88 top: theme.spacing(2),
89 right: theme.spacing(2),
90 }}
91 >
92 <IconButton color="primary" onClick={() => setIsEditing(true)}>
93 <TuneIcon />
94 </IconButton>
95 </Tooltip>
96 );
97
98 return (
99 <Box
100 sx={{
101 position: 'relative',
102 }}
103 >
104 <Container
105 sx={{
106 p: 4,
107 mt: 6,
108 mb: 11,
109 mx: 0,
110 [theme.breakpoints.down('md')]: {
111 p: 2,
112 },
113 }}
114 >
115 <Card
116 sx={{
117 position: 'relative',
118 maxWidth: '100%',
119 width: '350px',
120 p: 2,
121 }}
122 >
123 <Typography variant="h4" pb={2}>
124 {t('event.details')}
125 </Typography>
126 {modifyButton}
127 <Box pt={2} pr={1.5}>
128 <Typography variant="overline">{t('event.fields.name')}</Typography>
129 <Typography variant="body1">
130 {isEditing ? (
131 <TextField
132 size="small"
133 fullWidth
134 value={event.name}
135 onChange={e => setEventUpdate({name: e.target.value})}
136 name="name"
137 id="EditEventName"
138 />
139 ) : (
140 <Typography variant="body1" id="EventName">
141 {event.name ?? t('event.fields.empty')}
142 </Typography>
143 )}
144 </Typography>
145 </Box>
146 <Box pt={2} pr={1.5}>
147 <Typography variant="overline">{t('event.fields.date')}</Typography>
148 {isEditing ? (
149 <Typography variant="body1">
150 <DatePicker
151 slotProps={{
152 textField: {
153 size: 'small',
154 id: `EditEventDate`,
155 fullWidth: true,
156 placeholder: t('event.fields.date_placeholder'),
157 },
158 }}
159 format="DD/MM/YYYY"
160 value={moment(event.date)}
161 onChange={date =>
162 setEventUpdate({
163 date: !date ? null : moment(date).format('YYYY-MM-DD'),
164 })
165 }
166 />
167 </Typography>
168 ) : (
169 <Box position="relative">
170 <Typography variant="body1" id="EventDate">
171 {event.date
172 ? moment(event.date).format('DD/MM/YYYY')
173 : t('event.fields.empty')}
174 </Typography>
175 <EventIcon
176 color="action"
177 sx={{
178 position: 'absolute',
179 right: theme.spacing(-0.5),
180 top: 0,
181 }}
182 />
183 </Box>
184 )}
185 </Box>
186 <Box pt={2} pr={1.5}>
187 <Typography variant="overline">
188 {t('event.fields.address')}
189 </Typography>
190 {isEditing ? (
191 <PlaceInput
192 place={event.address}
193 latitude={event.latitude}
194 longitude={event.longitude}
195 onSelect={({place, latitude, longitude}) =>
196 setEventUpdate({
197 address: place,
198 latitude,
199 longitude,
200 })
201 }
202 />
203 ) : (
204 <Box position="relative">
205 <Typography variant="body1" id="EventAddress" sx={{pr: 3}}>
206 {event.address ? (
207 <Link
208 target="_blank"
209 rel="noreferrer"
210 href={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(
211 event.address
212 )}`}
213 onClick={e => e.preventDefault}
214 >
215 {event.address}
216 </Link>
217 ) : (
218 t('event.fields.empty')
219 )}
220 </Typography>
221 <PlaceOutlinedIcon
222 color="action"
223 sx={{
224 position: 'absolute',
225 right: theme.spacing(-0.5),
226 top: 0,
227 }}
228 />
229 </Box>
230 )}
231 </Box>
232 <Box pt={2} pr={1.5}>
233 <Typography variant="overline">
234 {t('event.fields.description')}
235 </Typography>
236 {isEditing ? (
237 <Typography variant="body1">
238 <TextField
239 fullWidth
240 multiline
241 maxRows={4}
242 inputProps={{maxLength: 250}}
243 value={event.description || ''}
244 onChange={e => setEventUpdate({description: e.target.value})}
245 id={`EditEventDescription`}
246 name="description"
247 />
248 </Typography>
249 ) : (
250 <Typography variant="body1" id="EventDescription" sx={{pr: 3}}>
251 {event.description ?? t('event.fields.empty')}
252 </Typography>
253 )}
254 </Box>
255 {!isEditing && (
256 <ShareEvent
257 title={`Caroster ${event.name}`}
258 sx={{width: '100%', mt: 2}}
259 />
260 )}
261 </Card>
262 </Container>
263 </Box>
264 );
265};
266
267export const getServerSideProps = pageUtils.getServerSideProps(
268 async (context, apolloClient) => {
269 const {uuid} = context.query;
270 const {host = ''} = context.req.headers;
271 let event = null;
272
273 // Fetch event
274 try {
275 const {data} = await apolloClient.query({
276 query: EventByUuidDocument,
277 variables: {uuid},
278 });
279 event = data?.eventByUUID?.data;
280 } catch (error) {
281 return {
282 notFound: true,
283 };
284 }
285
286 return {
287 props: {
288 eventUUID: uuid,
289 metas: {
290 title: event?.attributes?.name || '',
291 url: `https://${host}${context.resolvedUrl}`,
292 },
293 },
294 };
295 }
296);
297export default Page;